package db

import (
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"sort"
	"strconv"
	"strings"
	"time"
	"ulcdkey/conf"
	"ulcdkey/tool"
)

var WorkerList = []*tool.Worker{}

func init() {
	// 根据配置生成若干个cdk worker
	for i := 0; i < conf.Conf.CdkWorkerNum; i++ {
		var cdk = new(Cdk)
		var worker = tool.NewWorker(conf.Conf.WorkerChanLength, cdk.CdkWorkerCallBack)
		WorkerList = append(WorkerList, worker)
		worker.Start(1) // start中开启线程
	}
}

func (this *Cdk) GetWorkerlength() (msg string) {

	for i := 0; i < len(WorkerList); i++ {
		w := WorkerList[i]
		l := w.GetChannelLength()

		msg += fmt.Sprintf("WorkerLengthList队列%d长度: %d  \n", i, l)
	}

	return
}

//var timeoutCount int64 = 0

// cdk worker回调函数
func (this *Cdk) CdkWorkerCallBack(data interface{}) {
	task := data.(*WorkerTask)

	if task.Status == -1 {
		// 任务已超时，drop
		//atomic.AddInt64(&timeoutCount, 1)
		//log.Println("CdkWorkerCallBack Status -1 timeoutCount:", timeoutCount)
		return
	} else {

		// 开始执行任务
		task.Status = 1
	}

	var resc = new(CdkRes)
	resc.Code, resc.Data = this.UseCdk(task)
	task.ResChannel <- resc
}

/*---------------cdk----------------------*/

// 根据cdk类获取cdk列表
func (this *Cdk) GetCdkListByTypeId(page, limit, status int) (as []*Cdk, counts int, err error) {
	var sqlStr string
	nCdkType := new(CdkType)
	sCdkType, err := nCdkType.GetCdkTypeById(this.CdkTypeId)
	if err != nil {
		log.Println("GetCdkListByTypeId1 db error,err=", err)
		return
	}

	games := ReadGameCacheById(sCdkType.GameId)

	if status == 2 {
		// 已过期的cdk，去mysql中查询
		tableName := fmt.Sprintf("ulcdkey_cdk.cdk_%s", games.GameCode)

		if page == 0 && limit == 0 {
			// 不进行分页查询
			sqlStr = fmt.Sprintf("select cdkTypeId,cdkey,canUseTimes,status,bAll from %s where cdkTypeId = %d", tableName, this.CdkTypeId)
		} else {
			// 分页查询
			sqlStr = fmt.Sprintf("select cdkTypeId,cdkey,canUseTimes,status,bAll from %s where cdkTypeId = %d limit %d,%d", tableName, this.CdkTypeId, (page-1)*limit, limit)
		}

		rows, err := db.Query(sqlStr)
		if err != nil {
			log.Println("GetCdkListByTypeId3 db error,err=", err)
			return as, counts, err
		}
		defer rows.Close()
		for rows.Next() {
			a := new(Cdk)
			err = rows.Scan(&a.CdkTypeId, &a.CdKey, &a.CanUseTimes, &a.Status, &a.B_all)
			if err != nil {
				log.Println("GetCdkListByTypeId4 db error,err=", err)
				return as, counts, err
			}
			as = append(as, a)
		}

		// 获取总条数
		sqlStr2 := fmt.Sprintf("select count(1) from %s where cdkTypeId = %d", tableName, this.CdkTypeId)
		err = db.QueryRow(sqlStr2).Scan(&counts)
		if err != nil {
			log.Println("GetCdkListByTypeId5 db error,err=", err)
			return as, counts, err
		}
	} else if status == 0 || status == 1 {
		// 未过期cdk数据，从redis中查询

		// cdk 清单表
		redisName := fmt.Sprintf("cdk_%s", games.GameCode)

		res, err := cdkDb.HGETALL(redisName)
		if err != nil {
			log.Println("GetCdkListByTypeId6 db error,err=", err)
			return as, counts, err
		}

		var keyArr []string
		cdkMap := make(map[string]*Cdk)
		for cdk, r := range res {
			var a *Cdk
			err = json.Unmarshal([]byte(r), &a)
			if err != nil {
				log.Println("GetCdkListByTypeId7 db error,err=", err)
				return as, counts, err
			}
			if a.Status == status && a.CdkTypeId == this.CdkTypeId {
				keyArr = append(keyArr, cdk)
				cdkMap[cdk] = a
				cdkMap[cdk].CdKey = cdk
			}
		}

		// 将cdk 字符串排序
		sort.Strings(keyArr)

		// 总条数
		counts = len(keyArr)

		if page == 0 && limit == 0 {
			// 不分页
			for _, cdk := range keyArr {
				as = append(as, cdkMap[cdk])
			}
		} else {
			// 分页查询
			start := (page - 1) * limit
			end := page * limit

			if len(keyArr) < start {
				return as, counts, err
			}
			if len(keyArr) < end {
				end = len(keyArr)
				if end-limit >= 0 {
					start = end - limit
				}
			}

			for _, cdk := range keyArr[start:end] {
				as = append(as, cdkMap[cdk])
			}
		}
	}

	return
}

/* 兑换返回状态
兑换成功 1
该cdkey不属于该应用  2
cdk不在有效使用时间内 3
cdk渠道不对，0渠道还有8888渠道才能通用。。 4
此cdkey此用户已使用过 5
roleId 为空 6
localCdk.Id != 0  7
*/
// 新增cdk
func (this *Cdk) AddSomeCdk(prefix string, nums, lens int, info *tool.VisitUserInfo) (err error) {
	nCdkType := new(CdkType)
	sCdkType, err := nCdkType.GetCdkTypeById(this.CdkTypeId)
	if err != nil {
		log.Println("AddSomeCdk1 db error,err=", err)
		return
	}

	// 查询游戏表中对应游戏 已经使用过的前缀名
	var bUsePrefixStr string
	var bUsePrefixArr []string
	err = db.QueryRow("select bUsePrefix from game where id = ?", sCdkType.GameId).Scan(&bUsePrefixStr)
	if err != nil {
		log.Println("AddSomeCdk1.1 db error,err=", err)
		return
	}
	if bUsePrefixStr != "" {
		bUsePrefixArr = strings.Split(bUsePrefixStr, ",")
	}

	// 同个游戏前缀不能重复
	for _, v := range bUsePrefixArr {
		// 前缀相等或存在包含关系
		if prefix == v || strings.Index(v, prefix) == 0 || strings.Index(prefix, v) == 0 {
			log.Println("AddSomeCdk1.2 db error,err=", err)
			return errors.New("该前缀在该游戏中已经被使用过或存在类似，请使用其他前缀")
		}
	}

	// 特殊cdk 如：888888 时 前缀长度可以为 0
	if lens != 0 && lens < 6 {
		log.Println("AddSomeCdk1.3 db error,err=", err)
		return errors.New("除前缀外长度只能输入0或者大于等于6的值，请修改长度")
	}

	if lens >= 30 {
		log.Println("AddSomeCdk1.3 db error,err=", err)
		return errors.New("除前缀外长度太长，超过30位，请修改长度")
	}

	if lens == 0 && nums > 1 {
		log.Println("AddSomeCdk1.4 db error,err=", err)
		return errors.New("除前缀外长度为0的特殊cdk如：888888，单次只能生成一条，请修改数量")
	}

	// 插入cdk到redis缓存

	// 开启redis事务
	c := cdkDb.pool.Get()
	err = c.Send("MULTI")
	if err != nil {
		return
	}

	defer func() {
		// 执行事务
		c.Do("EXEC")
		c.Close()
	}()

	// 从游戏表缓存中读取游戏
	games := ReadGameCacheById(sCdkType.GameId)
	for i := 0; i < nums; i++ {
		tryTimes := 0
	re:
		nCdk := prefix + tool.CreateCdk(lens)
		redisName := fmt.Sprintf("cdk_%s", games.GameCode)
		redisKey := nCdk
		redisValue := fmt.Sprintf(`{"cdkTypeId":%d,"status":0,"canUseTimes":%d,"bAll":%d}`, this.CdkTypeId, sCdkType.BUseTimes, sCdkType.BUseTimes)

		isExist, err := cdkDb.HExists(redisName, redisKey)
		if err != nil {
			log.Println("HEXISTS error")
			// 取消事务
			c.Send("DISCARD")
			return err
		}
		// 如果 cekey 已经存在
		if isExist {
			if tryTimes < 3 {
				tryTimes++
				goto re
			} else {
				log.Println("AddSomeCdk3 db error,err=", err)
				// 取消事务
				c.Send("DISCARD")
				return errors.New("cdk生成失败，请尝试使用其他前缀")
			}
		}

		// 插入
		err = c.Send("HSET", redisName, redisKey, redisValue)
		if err != nil {
			log.Println("HSET error")
			// 取消事务
			c.Send("DISCARD")
			return err
		}
	}

	// 更新游戏表中 bUsePrefix 字段
	nUsePrefixStr := strings.Join(append(bUsePrefixArr, prefix), ",")
	_, err = db.Exec("update game set bUsePrefix = ? where id = ?", nUsePrefixStr, sCdkType.GameId)
	if err != nil {
		log.Println("AddSomeCdk4 db error,err=", err)
		return
	}

	// 记录日志
	serverLog := &ServerLog{UserId: info.Uid, UserName: info.UserName, ActionType: LogActionType.Add, Module: "CDK管理", OldContent: "", NewContent: fmt.Sprintf("新增cdkTypeId：%d，前缀：%s 的cdk %d条", this.CdkTypeId, prefix, nums)}
	serverLog.RecordLog()

	return
}

/* 兑换返回状态
兑换成功 1
该cdkey不属于该应用  2
cdk不在有效使用时间内 3
cdk渠道不对，0渠道还有8888渠道才能通用。。 4
此cdkey此用户已使用过 5
无效cdk 6
版本号过低 7

*/

// 使用cdk
func (this *Cdk) UseCdk(task *WorkerTask) (code string, data interface{}) {

	code = "999" //系统错误
	data = "兑换失败"

	gameId, err := strconv.Atoi(task.AppId)
	if err != nil {
		log.Println("UseCdk-1 db error,err=", err)
		code = "2" //

		return
	}

	nGames := ReadGameCacheById(gameId)
	if nGames == nil {
		log.Println("UseCdk-2 db error,err=缓存中没有找到该游戏", err)
		return
	}

	gameCode := nGames.GameCode

	// cdk 清单表
	redisName := fmt.Sprintf("cdk_%s", gameCode)

	// step1 cdk清单表中查询是否有该条cdk，该条cdk剩余使用次数，如果次数等于0，则校验失败，如果不等于0且status为0代表可使用，则校验成功

	var nCdkInfo CdkInfo

	cdkTypeStr, err := cdkDb.HGet(redisName, task.Cdkey)
	if err != nil {
		log.Println("UseCdk3.0 db error,err=", err)
		return
	}

	if cdkTypeStr == "" {
		log.Println("UseCdk3.0 db error,err=清单中没有找到该条cdk")
		code = "6" //
		return
	}

	err = json.Unmarshal([]byte(cdkTypeStr), &nCdkInfo)
	if err != nil {
		log.Println("UseCdk3.1 db error,err=", err)
		return
	}

	if nCdkInfo.CanUseTimes <= 0 || nCdkInfo.Status != 0 {
		log.Println("UseCdk3.2 db error,err=cdk已失效，status == 0 || canUseTimes <=0")
		code = "6" //
		return
	}

	// step2 cdk类表中查询该类cdk有效时间进行校验 gameVersion进行校验
	cdkTypeMap := ReadCdkTypeCacheByGameId(gameId)
	if cdkTypeMap == nil {
		log.Println("UseCdk3.3 db error,err=", err)
		return
	}

	nCdkType := cdkTypeMap[nCdkInfo.CdkTypeId]
	if nCdkType == nil {
		log.Println("UseCdk3.4 db error,err=", err)
		return
	}

	startDate := nCdkType.StartDate
	endDate := nCdkType.EndDate
	rewardList := nCdkType.RewardList
	userLimitCount := nCdkType.UserLimitCount
	canUseCount := nCdkType.CanUseCount
	typeGameVersion := nCdkType.GameVersion

	timeLayout := "2006-01-02 15:04:05"

	//  如果cdk的type 渠道 不是全渠道通用则校验 渠道id
	if !nCdkType.ChannelId.IsAll {

		flag := nCdkType.checkChannelId(task.Platform, task.ChannelId)
		if !flag {

			code = "4" //
			return
		}
	}

	if task.GameVersion < typeGameVersion {
		code = "7" //无效cdk
		return
	}

	sTime, err := time.ParseInLocation(timeLayout, startDate, time.Local)
	if err != nil {
		log.Println("UseCdk5 db error,err=", err)
		return
	}
	eTime, err := time.ParseInLocation(timeLayout, endDate, time.Local)
	if err != nil {
		log.Println("UseCdk6 db error,err=", err)
		return
	}
	nTime := time.Now()

	// 如果使用时间在有效期范围外
	if nTime.Before(sTime) {

		code = "3" //
		return
	}
	if nTime.After(eTime) || canUseCount <= 0 {
		code = "3" //
		return
	}

	// step3 查询用户是否使用过该条cdk，

	// cdk 使用表
	redisUseName := fmt.Sprintf("cdkuse_%s", gameCode)

	bTypeUseTimes := 0
	redisValueMap := make(map[string]map[string]string)
	bTypeId := fmt.Sprint(nCdkInfo.CdkTypeId)

	//用户使用过的cdk
	userCdkMapStr, err := cdkDb.HGet(redisUseName, task.UserId)
	if err != nil {
		log.Println("UseCdk7.0 db error,err=", err)
		return
	}

	// 如果存在对应的cdkd使用表中和表中的用户map不存在则直接校验通过
	if userCdkMapStr != "" {

		err = json.Unmarshal([]byte(userCdkMapStr), &redisValueMap)
		if err != nil {
			log.Println("UseCdk7.1 db error,err=", err)
			return
		}

	}

	userTypeCdks := redisValueMap[bTypeId]
	// 如果该用户 没有 使用该类cdk的记录存在
	if userTypeCdks == nil {
		//则初始化当前cdk类的map
		redisValueMap[bTypeId] = make(map[string]string)
	}

	_, ok2 := redisValueMap[bTypeId][task.Cdkey]
	// 用户已经使用过该条cdk
	if ok2 {
		log.Println("UseCdk7.2 db error,err=您已经使用过该条cdk")
		code = "5"
		return
	}

	// 如果没有使用过该条，统计cdk使用表中此用户该类cdk使用次数，查询cdk类表userLimitCount,如果超过次数，则校验失败
	bTypeUseTimes = len(redisValueMap[bTypeId])
	if bTypeUseTimes >= userLimitCount {
		log.Println("UseCdk7.3 db error,err=您无法再使用此类cdk")

		code = "5"
		return
	}

	// step4 校验成功

	// 开启redis事务
	c := cdkDb.pool.Get()
	err = c.Send("MULTI")
	if err != nil {

		return
	}
	defer func() {
		// 执行事务
		c.Do("EXEC")
		c.Close()
	}()

	// cdk使用表中插入记录
	nowUseTime := nTime.Format(timeLayout)

	redisValueMap[bTypeId][task.Cdkey] = nowUseTime

	valueBytes, err := json.Marshal(redisValueMap)
	if err != nil {
		log.Println("UseCdk8 db error,err=", err)
		return
	}
	redisValue := string(valueBytes)
	err = c.Send("HSET", redisUseName, task.UserId, redisValue)
	if err != nil {
		log.Println("HSET 插入使用表 error,err=", err)
		// 取消事务
		c.Send("DISCARD")
		return
	}

	/*
		TODO 清单中如果一个cdk使用完，cdk类中canUseCount减少1 《 暂不处理cdk类表中的totalCount、canUseCount字段 》
	*/

	if task.IsTimeout {

		log.Println("task.IsTimeout :", task.IsTimeout)
		return
	}

	nCdkInfo.CanUseTimes--

	// 更新cdk清单表
	if nCdkInfo.CanUseTimes == 0 {
		//   0 可使用 1 已使用 2 已生成但不可使用
		nCdkInfo.Status = 1
	}

	typeValueBytes, err := json.Marshal(nCdkInfo)
	redisTypeValue := string(typeValueBytes)
	err = c.Send("HSET", redisName, task.Cdkey, redisTypeValue)
	if err != nil {
		log.Println("HSET 更新清单表 error，err=", err)
		// 取消事务
		c.Send("DISCARD")
		return
	}

	//// step5 返回奖励列表
	//bytes, err := json.Marshal(rewardList)
	//if err != nil {
	//	log.Println("UseCdk10 db error,err=", err)
	//	return
	//}

	code = "1" //领取成功
	data = rewardList
	return
}
